home *** CD-ROM | disk | FTP | other *** search
- #!/usr/local/bin/stk -f
-
- ; -* Lisp -*-
-
- ; Yet another "my first STk program" type thing. This one is the "8
- ; queens" puzzle. You try to figure out how to place 8 queens on a
- ; chessboard so that none of the queens can be taken in a single move.
- ;
- ; You can do it yourself (and it will make sure you follow the rules)
- ; or you can ask it to solve the puzzle starting with a given board
- ; configuration.
- ;
- ; It expects to fined the queen bitmap in the images directory
- ; in the STk library directory.
-
- ; 27 Jan 96: ported to STk 3.0
-
- ; Grant Edwards
- ;
- ; grante@winternet.com
- ; grante@rosemount.com
- ; grante@ep.frco.com
- ; edwards@grad.cs.umn.edu
-
-
-
- (define queen-bitmap (string-append "@" *STk-library* "/images/queen"))
-
- ; size of board (it's square)
-
- (define size 8)
-
-
- ; Predicate that is true if the queens at p1 and p2 can't take each
- ; other in 1 move. p1 and p2 are pairs of the form ( x . y ) where
- ; x is column and y is row (both from 0 to size-1).
-
- (define legal-position-pair?
- (lambda (p1 p2)
- (let ([x1 (car p1)] [y1 (cdr p1)] [x2 (car p2)] [y2 (cdr p2)])
- (not (or
- (= x1 x2)
- (= y1 y2)
- (= (abs (- x1 x2)) (abs (- y1 y2))))))))
-
-
- ; Predicate that is true if none of the queens in list history can
- ; take queen at postion new in one move. "history" is a list of
- ; position pairs. "new" is the position pair which we are testing.
-
- (define legal-move?
- (lambda (history new)
- (cond
- [(null? history) #t]
- [(not (legal-position-pair? (car history) new)) #f]
- [else (legal-move? (cdr history) new)])))
-
-
- ; This is the procedure that solves the puzzle given a list of
- ; occupied squares and a list of empty rows. It's also passed a
- ; continuation so that it can abort when the user asks it to stop.
-
- ; Add a legal move to history list and recurse to build up strings of
- ; legal moves. The chessboard is updated as pieces are placed. When
- ; it reaches the required length, it waits for user to press the Next
- ; or Stop button. "history" is a list of pairs that denotes where
- ; there are already queens. "ylist" is a list of rows that still need
- ; to be filled. "break" is a continuation to be called when the
- ; procedure is to be aborted.
-
- (define add-queen
- (lambda (history ylist break)
- (cond
- [stopPushed (break #f)]
- [(null? ylist) (begin (write history)(newline)(waitForNextButton)(if stopPushed (break #f)))]
- [else (let ([newy (car ylist)])
- (dotimes (newx size)
- (if (legal-move? history (cons newx newy))
- (begin
- (activate-button newx newy)
- (update)
- (add-queen (cons (cons newx newy) history) (cdr ylist) break)
- (deactivate-button newx newy)
- (update)))))])))
-
- ; global boolean used to keep track of whether or not the user is
- ; allowed to rearrange the board.
-
- (define userModsEnabled #t)
-
-
- ; set up button states and solve the puzzle starting with the current
- ; board configuration.
-
- (define do-solve
- (lambda ()
- (set! stopPushed #f)
- (.upper.solve 'configure :state 'disabled)
- (.upper.stop 'configure :state 'normal)
- (.upper.clear 'configure :state 'disabled)
- (set! userModsEnabled #f)
- (call/cc (lambda (break)(add-queen (current-positions)(empty-rows) break)))
- (.upper.stop 'configure :state 'disabled)
- (.upper.clear 'configure :state 'normal)
- (set! userModsEnabled #t)
- (.upper.solve 'configure :state 'normal)))
-
-
- ; define some procedures to create and operate on matrixes
-
- (define make-matrix
- (lambda (i j v)
- (let ([m (make-vector i)])
- (dotimes (c j m)
- (vector-set! m c (make-vector j v))))))
-
- (define matrix-ref
- (lambda (m i j)
- (vector-ref (vector-ref m i) j)))
-
- (define matrix-row
- (lambda (m i)
- (vector-ref m i)))
-
- (define matrix-set!
- (lambda (m i j v)
- (vector-set! (vector-ref m i) j v)))
-
-
- ; Create two matrixes. Each has an entry for each square on the
- ; board. One matrix is Tk button procedures, the other is booleans
- ; that reflect whether or not the square is occupied.
-
- (define board-buttons (make-matrix size size #f))
- (define board-states (make-matrix size size #f))
-
-
- ; redraw the button so that it is occupied and update the matrix of
- ; booleans
-
- (define activate-button
- (lambda (x y)
- ((matrix-ref board-buttons y x) 'configure :relief 'raised :foreground "#000")
- (matrix-set! board-states y x #t)))
-
-
- ; redraw the button so that it is empty and update the matrix of
- ; booleans
-
- (define deactivate-button
- (lambda (x y)
- (let* ([b (matrix-ref board-buttons y x)]
- [bg (cadr (cdddr (b 'configure :background)))])
- (b 'configure :relief 'flat :foreground bg)
- (matrix-set! board-states y x #f))))
-
- ; flash a button
-
- (define flash-button
- (lambda (x y)
- ((matrix-ref board-buttons y x) 'flash)))
-
-
- ; Procedure called when the user clicks on a square in the chessboard.
- ; If user modifications are not enabled, then do nothing. Otherwise
- ; toggle the sate of the square. When placing a queen on a previously
- ; empty square, remove existing queens that could be taken by the new
- ; one.
-
- (define toggle-button
- (lambda (x y)
- (cond
- [ (not userModsEnabled) #f]
- [ (matrix-ref board-states y x) (deactivate-button x y)]
- [else (begin
- (activate-button x y)
- (update)
- (dotimes (ox size)
- (dotimes (oy size)
- (if (and
- (matrix-ref board-states oy ox)
- (not (and (= x ox) (= y oy)))
- (not (legal-position-pair? (cons x y) (cons ox oy))))
- (begin
- (flash-button ox oy)
- (flash-button ox oy)
- (flash-button ox oy)
- (deactivate-button ox oy)
- (update))))))])))
-
-
- ; clear the board
-
- (define clear-board
- (lambda ()
- (dotimes (x size) (dotimes (y size) (deactivate-button x y)))))
-
-
- ; Procedures to return a list of consecutive integers from start to
- ; end (inclusive).
-
- (define interval
- (lambda (start end)
- (let loop ([s start] [e end] [l ()])
- (if (> s e) l (loop s (- e 1) (cons e l))))))
-
- (define rinterval
- (lambda (start end)
- (let loop ([s start] [e end] [l ()])
- (if (> s e) l (loop (+ s 1) e (cons s l))))))
-
-
- ; Return a list of integers that identify the rows on the chessboard
- ; that are empty
-
- (define empty-rows
- (lambda ()
- (let loop ([rows (rinterval 0 (- size 1))] [empty ()])
- (if (null? rows)
- empty
- (if (member #t (vector->list (matrix-row board-states (car rows))))
- (loop (cdr rows) empty)
- (loop (cdr rows) (cons (car rows) empty)))))))
-
-
- ; Return a list of pairs ( x . y ) indicating which squares are
- ; currently occupied.
-
- (define current-positions
- (lambda ()
- (let ([p ()])
- (dotimes (x size)
- (dotimes (y size)
- (if (matrix-ref board-states y x) (set! p (cons (cons x y) p)))))
- p)))
-
-
- ; Booleans used to detect when user presses a button
-
- (define nextOrStopPushed #f)
- (define stopPushed #f)
-
-
- ; Procedure to wait for the user to press either the next or stop
- ; buttons.
-
- (define waitForNextButton
- (lambda ()
- (.upper.next 'configure :state 'normal)
- (tkwait 'variable 'nextOrStopPushed)
- (.upper.next 'configure :state 'disabled)))
-
-
- ; Define two frames. The upper will hold control buttons, the lower
- ; the chessboard buttons
-
- (frame '.lower :relief 'raised :borderwidth 2)
- (frame '.upper)
-
- ; procedure that does nothing other than return the break symbol
-
- (define noop-break (lambda () 'break))
-
-
- ; add a frame to the lower frame for each row of sqaures on the
- ; chessboard and fill that row with buttons (one per square).
-
- (dotimes (y size)
- (let ([rowframe (format #f ".lower.row~a" y)])
- (frame rowframe)
- (dotimes (x size)
- (let* ([bn (format #f "~a.b~a" rowframe x)]
- [bp (eval (button bn
- :bitmap queen-bitmap
- :highlightthickness 0
- :relief 'flat))])
- (matrix-set! board-buttons y x bp)
- (let ([bg (if (odd? (+ x y)) "#bbb" "#eee")])
- (bp 'configure :background bg :activebackground "#fff" :foreground bg))
- (bind bn "<Button-1>" (lambda () (toggle-button x y) 'break))
- (bind bn "<Any-Enter>" noop-break)
- (bind bn "<Any-Leave>" noop-break)
- (bind bn "<ButtonRelease-1>" noop-break)
- (pack bn :side 'left)
- )
- )
- (pack rowframe :side 'bottom)
- )
- )
-
-
- ; add control buttons to upper frame
-
- (button '.upper.quit :text "Quit" :command (lambda () (exit)))
- (button '.upper.solve :text "Solve" :command do-solve)
- (button '.upper.Clear :text "Clear" :command clear-board)
- (button '.upper.next
- :text "Next"
- :state 'disabled
- :command (lambda () (set! stopPushed #f)(set! nextOrStopPushed #t)))
- (button '.upper.stop
- :text "Stop"
- :state 'disabled
- :command (lambda () (set! stopPushed #t)(set! nextOrStopPushed #t)))
- (frame '.upper.fill)
- (pack '.upper.solve :side 'left)
- (pack '.upper.next :side 'left)
- (pack '.upper.stop :side 'left)
- (pack '.upper.clear :side 'left)
- (pack '.upper.quit :side 'right)
- (pack '.upper.fill :side 'right)
-
- ; arrange the two top level frames
-
- (pack '.upper :side 'top :fill 'x)
- (pack '.lower :side 'bottom)
-
-
-